#!/usr/bin/python3
import typing as T
from pathlib import Path
import os
import urllib.parse
import re
import argparse
import requests
import tarfile
from jinja2 import Template
import dnf
import locale
from datetime import datetime
from scancode import api as scancode_api

'''
    cmake2spec is a program that can parse CMakeLists.txt and collect infomation, then it
    can generate a rpm spec file.
    This program adapt to syntax of Cmake 3.30.
    Basic syntax of Cmake: https://cmake.org/cmake/help/latest/manual/cmake-language.7.html
    Original program: https://github.com/mesonbuild/meson/blob/master/tools/cmake2meson.py
    Author: xiaoji (shouhuanxiaoji@gmail.com)
    Adress: Beijing, China
'''
ENABLE_DEBUG = 0

Clists = {
    "parsed": [],
    "unparsed": []
}

# 定义 CMake 内置变量
def get_cmake_builtin_variables(cmake_file_path: Path) -> dict:
    """
    Get Cmake built-in variable

    Parameters:
        cmake_file_path (Path): path of CMakeLists.txt or module file,
        it is a pathlib.Path type value
    Return value:
        dict: a dict involved name and value of Cmake built-in variable
    """
    cmake_source_dir = cmake_file_path.resolve().parent
    return {
        'CMAKE_SOURCE_DIR': str(cmake_source_dir),
        'CMAKE_BINARY_DIR': str(cmake_source_dir),
        'CMAKE_CURRENT_SOURCE_DIR': str(cmake_source_dir),
        'CMAKE_CURRENT_BINARY_DIR': str(cmake_source_dir),
    }

class Token:
    """
    表示词法分析器生成的 Token。

    属性:
        tid (str): Token 的类型标识符。
        value (str): Token 的值。
        lineno (int): Token 所在的行号。
        colno (int): Token 所在的列号。
    """
    def __init__(self, tid: str, value: str, lineno: int = 0, colno: int = 0):
        self.tid = tid
        self.value = value
        self.lineno = lineno
        self.colno = colno

class Statement:
    """
    表示语法分析器生成的语句。

    属性:
        name (str): 语句的名称（小写）。
        args (list): 语句的参数列表。
    """
    def __init__(self, name: str, args: list):
        self.name = name.lower()
        self.args = args

class Lexer:
    """
    CMake 词法分析器。

    属性:
        token_specification (list): 词法规则列表。
    """
    def __init__(self) -> None:
        self.token_specification = [
            # Need to be sorted longest to shortest.
            ('ignore', re.compile(r'[ \t]')),
            # 字符串
            ('string', re.compile(r'"([^\\]|(\\.))*?"', re.M)),
            # 标识符(identifier)，包括命名变量、函数、类、模块(if/endif, foreach/endforeach)、类型等程序实体的名称
            ('id', re.compile('''[,-><&$%\?\[\]{}=+_0-9a-z/A-Z|@.*]+''')),
            # 变量
            ('varexp', re.compile(r'\${[-_0-9a-z/A-Z.]+}')),
            # 换行
            ('eol', re.compile(r'\n')),
            # 注释
            ('comment', re.compile(r'#.*')),
            ('lparen', re.compile(r'\(')),
            ('rparen', re.compile(r'\)')),
        ]

    def lex(self, code: str) -> T.Iterator[Token]:
        """
        对输入的代码进行词法分析，生成 Token 流。

        参数:
            code (str): 输入的 CMake 代码。

        返回:
            Iterator[Token]: 生成的 Token 流。
        """
        lineno = 1
        line_start = 0
        loc = 0
        col = 0
        lines = code.splitlines()
        while loc < len(code):
            matched = False
            for (tid, reg) in self.token_specification:
                mo = reg.match(code, loc)
                if mo:
                    col = mo.start() - line_start
                    matched = True
                    loc = mo.end()
                    match_text = mo.group()
                    if tid == 'ignore':
                        continue
                    if tid == 'comment':
                        yield(Token('comment', match_text, lineno, col))
                    elif tid == 'lparen':
                        yield(Token('lparen', '(', lineno, col))
                    elif tid == 'rparen':
                        yield(Token('rparen', ')', lineno, col))
                    elif tid == 'string':
                        yield(Token('string', match_text[1:-1], lineno, col))
                    elif tid == 'id':
                        yield(Token('id', match_text, lineno, col))
                    elif tid == 'varexp':
                        yield(Token('varexp', match_text[2:-1], lineno, col))
                    elif tid == 'eol':
                        lineno += 1
                        col = 1
                        line_start = mo.end()
                    else:
                        raise ValueError(f'lex: unknown element {tid}')
                    break
            if not matched:
                error_line = lines[lineno - 1] if lineno <= len(lines) else ''
                raise ValueError(f'Lexer got confused at line {lineno}, column {col}.\nLine content: {error_line}')

class Parser:
    """
    CMake 语法分析器。

    属性:
        stream (Iterator[Token]): 词法分析器生成的 Token 流。
        current (Token): 当前处理的 Token。
    """
    def __init__(self, code: str) -> None:
        self.stream = Lexer().lex(code)
        self.getsym()

    def getsym(self) -> None:
        """
        获取下一个 Token。
        """
        try:
            self.current = next(self.stream)
        except StopIteration:
            self.current = Token('eof', '')

    def accept(self, s: str) -> bool:
        """
        检查当前 Token 是否为指定类型，并获取下一个 Token。

        参数:
            s (str): 期望的 Token 类型。

        返回:
            bool: 如果当前 Token 类型匹配，返回 True，否则返回 False。
        """
        if self.current.tid == s:
            self.getsym()
            return True
        return False

    def expect(self, s: str) -> bool:
        """
        检查当前 Token 是否为指定类型，并获取下一个 Token。如果不匹配，抛出异常。

        参数:
            s (str): 期望的 Token 类型。

        返回:
            bool: 如果当前 Token 类型匹配，返回 True。

        异常:
            ValueError: 如果当前 Token 类型不匹配，抛出异常。
        """
        if self.accept(s):
            return True

        raise ValueError(f'Expecting {s} got {self.current.tid}. Line: {self.current.lineno}, Column: {self.current.colno}')

    def statement(self) -> Statement:
        cur = self.current
        if self.accept('comment'):
            return Statement('_', [cur.value])
        self.accept('id')
        args = []
        depth = 0
        while True:
            if self.accept('lparen'):
                depth += 1
                args += self.arguments()
            elif self.accept('rparen'):
                depth -= 1
                if depth == 0:
                    break
            else:
                break
        return Statement(cur.value, args)

    def arguments(self) -> T.List[T.Union[Token, T.Any]]:
        args: T.List[T.Union[Token, T.Any]] = []
        if self.accept('lparen'):
            args.append(self.arguments())
        arg = self.current
        if self.accept('comment'):
            rest = self.arguments()
            args += rest
        elif self.accept('string') \
                or self.accept('varexp') \
                or self.accept('id'):
            args.append(arg)
            rest = self.arguments()
            args += rest
        return args

    def parse(self) -> T.Iterator[Statement]:
        """
        解析整个 CMake 代码，生成语句流。

        返回:
            Iterator[Statement]: 生成的语句流。
        """
        while not self.accept('eof'):
            yield(self.statement())

def parser_to_dict(t: T.List[Statement]) -> T.Dict[dict, T.Any]:
    to_dict = {}
    for statement in t:
        preincrement = 0
        postincrement = 0
        indent_unit = '  '
        indent_level = 0
        if statement.name == '_':
            continue
        if statement.name == 'message':
            # 不需要解析打印信息
            continue
        elif statement.name == 'cmake_minimum_required':
            if not "cmake_minimum_required" in to_dict:
                to_dict['cmake_minimum_required'] = statement.args[1].value
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
        elif statement.name == 'file':
            # 暂时不解析file
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'find_file':
            # 暂时不解析find_file
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'get_filename_component':
            # get_filename_component在3.20被cmake_path()替代
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'include':
            # 暂时不解析include
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'checkincludefiles':
            # 暂不支持
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'check_symbol_exists':
            # 暂不支持
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'add_subdirectory':
            if not "add_subdirectory" in to_dict:
                to_dict["add_subdirectory"] = []
            to_dict["add_subdirectory"].append(statement.args[0].value)
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                print(statement.args[0].value)
        elif statement.name == 'pkg_search_module' or statement.name == 'pkg_search_modules':
            continue
            varname = statement.args[0].value.lower()
            mods = ["dependency('%s')" % i.value for i in statement.args[1:]]
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
        elif statement.name == 'find_package':
            if not "find_package" in to_dict:
                to_dict["find_package"] = {}
            if not statement.args[0].value in to_dict["find_package"]:
                # 其他参数暂时还不支持，如果有需要可继续添加
                to_dict["find_package"][statement.args[0].value] = {
                    "version": "",
                    "components": [],
                    "required": False
                }
            if len(statement.args) > 1:
                find_package_args_list = []
                for arg in statement.args:
                    find_package_args_list.append(arg.value)
                # match the version, if it exist, it will be the second parameter
                pattern_version = r'\d'
                match_version = re.search(pattern_version, find_package_args_list[1])
                if match_version:
                    to_dict["find_package"][statement.args[0].value]["version"] = find_package_args_list[1]
                if "REQUIRED" in find_package_args_list:
                    to_dict["find_package"][statement.args[0].value]["required"] = True
                if "COMPONENTS" in find_package_args_list:
                    index_components = find_package_args_list.index("COMPONENTS")
                    # 有可能出现COMPONENTS之后组件为空的情况
                    if not index_components == (len(find_package_args_list) -1):
                        to_dict["find_package"][statement.args[0].value]["components"] = find_package_args_list[index_components+1:]

            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
        elif statement.name == 'add_dependencies':
            # 解析该字段暂时没有用
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'find_program':
            if not "find_program" in to_dict:
                to_dict["find_program"] = []
            list(to_dict["find_program"]).append(statement.args[1].value)
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
        elif statement.name == 'find_library':
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'add_executable':
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'add_library':
            if statement.args[1].value == 'SHARED':
                libcmd = 'shared_library'
                args = [statement.args[0]] + statement.args[2:]
            elif statement.args[1].value == 'STATIC':
                libcmd = 'static_library'
                args = [statement.args[0]] + statement.args[2:]
            else:
                libcmd = 'library'
                args = statement.args
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
                print(libcmd)
        elif statement.name == 'add_test':
            # 暂时不支持test解析
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'option':
            # 不解析编译选项参数
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'target_compile_options':
            # 不解析编译选项参数
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'project':
            if not "project" in to_dict:
                to_dict["find_program"] = {
                    "name": "",
                    "version": "",
                    "description": "",
                    "homepage_url": "",
                    "languages": []
                }
            if len(statement.args) == 1:
                to_dict["find_program"]["name"] = statement.args[0].value
            if len(statement.args) > 1:
                to_dict["find_program"]["name"] = statement.args[0].value
                # 简易模式
                if (not statement.args[1].value == "VERSION") \
                and (not statement.args[1].value == "DESCRIPTION") \
                and (not statement.args[1].value == "HOMEPAGE_URL") \
                and (not statement.args[1].value == "LANGUAGES"):
                    lang_list = statement.args[1:]
                    for lang in lang_list:
                        to_dict["find_program"]["languages"].append(lang.value)
                # 详细模式
                else:
                    project_token_arg_value_list = []
                    for arg in statement.args:
                        project_token_arg_value_list.append(arg.value)
                    if "LANGUAGES" in project_token_arg_value_list:
                        index_lang = project_token_arg_value_list.index("LANGUAGES")
                        to_dict["languages"] = project_token_arg_value_list[index_lang+1:]
                    if "VERSION" in project_token_arg_value_list:
                        index_version = project_token_arg_value_list.index("VERSION")
                        to_dict["version"] = project_token_arg_value_list[index_version+1]
                    if "DESCRIPTION" in project_token_arg_value_list:
                        index_description = project_token_arg_value_list.index("DESCRIPTION")
                        to_dict["version"] = project_token_arg_value_list[index_description+1]
                    if "HOMEPAGE_URL" in project_token_arg_value_list:
                        index_homepage = project_token_arg_value_list.index("HOMEPAGE_URL")
                        to_dict["version"] = project_token_arg_value_list[index_homepage+1]
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
        elif statement.name == 'set':
            # 如果set语句只有变量名一个参数，没有值，则表示unset动作
            if not "vars" in to_dict:
                to_dict["vars"] = {}
            if len(statement.args) == 1:
                to_dict["vars"][statement.args[0].value] = None
            if len(statement.args) == 2:
                if not ";" in statement.args[1].value:
                    to_dict["vars"][statement.args[0].value] = ""
                    to_dict["vars"][statement.args[0].value] = statement.args[1].value
                else:
                    to_dict["vars"][statement.args[0].value] = []
                    vars_list = list(statement.args[1].value.split(";"))
                    to_dict["vars"][statement.args[0].value] = vars_list
            if len(statement.args) > 2:
                to_dict["vars"][statement.args[0].value] = []
                var_args = statement.args[1:]
                for arg in var_args:
                    to_dict["vars"][statement.args[0].value].append(arg.value)
            if ENABLE_DEBUG:
                print("vars set ----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
        elif statement.name == 'unset':
            if not "vars" in to_dict:
                to_dict["vars"] = {}
            if len(statement.args) == 1:
                to_dict["vars"][statement.name] = None            
            if ENABLE_DEBUG:
                print("vars unset----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
        elif statement.name == 'list':
            # 列表操作语句，暂时不支持
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'if':
            # 暂时不支持if解析
            if ENABLE_DEBUG:
                postincrement = 1
                try:
                    print("----------------------")
                    print(statement.name)
                    for arg in statement.args:
                        if type(arg) == "Token":
                            print(arg.value)
                        else:
                            print(arg)
                except AttributeError:  # complex if statements
                    line = statement.name
                    for arg in statement.args:
                        line += parser_to_dict(arg)
                    print("----------------------")
                    print(statement.name)
                    for arg in statement.args:
                        print(arg.value)
            continue
        elif statement.name == 'elseif':
            # 暂时不支持if解析
            if ENABLE_DEBUG:
                preincrement = -1
                postincrement = 1
                try:
                    print("----------------------")
                    print(statement.name)
                    for arg in statement.args:
                        print(arg.value)
                except AttributeError:  # complex if statements
                    line = statement.name
                    for arg in statement.args:
                        line += parser_to_dict(arg)
                    print("----------------------")
                    print(statement.name)
                    for arg in statement.args:
                        print(arg.value)
            continue
        elif statement.name == 'else':
            # 暂时不支持if解析
            if ENABLE_DEBUG:
                preincrement = -1
                postincrement = 1
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'endif':
            # 暂时不支持if解析
            if ENABLE_DEBUG:
                preincrement = -1
                line = 'endif'
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'foreach':
            # 暂时不支持foreach解析
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        elif statement.name == 'endforeach':
            # 暂时不支持foreach解析
            if ENABLE_DEBUG:
                print("----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        else:
            # 其他语句，未来逐一解决
            if ENABLE_DEBUG:
                print("other vars and macros----------------------")
                print(statement.name)
                for arg in statement.args:
                    print(arg.value)
            continue
        indent_level += preincrement
        indent_level += postincrement

    return to_dict

def multipath(path):
    # 尝试解析路径为 URL
    url_type = "unknown"
    url_result = urllib.parse.urlparse(path)
    if all([url_result.scheme, url_result.netloc]):
        url_type = "url"
    elif os.path.isdir(path):
        url_type = "dir"
    elif os.path.isfile(path):
        url_type = "file"
    else:
        url_type = "unkown"
    return url_type

def download_file(url, outdir, max_trys=3) -> dict:
    """
    下载文件并尝试从 Content-Disposition 获取文件名。
    使用 Content-Length 验证文件完整性。
    检查本地文件是否存在且完整，如果是，则不下载。
    
    :param url: 文件的 URL 地址
    :param outdir: 文件下载到的本地目录
    :return: 布尔值，表示文件是否下载成功或已存在且完整
    """
    file_info = {
        "save_path": "",
        "filename": ""
    }
    trys = 0
    while trys < max_trys:
        try:
            # 发起请求
            response = requests.get(url, stream=True)
            # 尝试从 Content-Disposition 获取文件名
            content_disposition = response.headers.get('Content-Disposition')
            if content_disposition:
                # 解析出文件名
                file_info["filename"] = content_disposition.split('filename=')[-1].strip('"')
            else:
                # 从 URL 中提取文件名
                file_info["filename"] = os.path.basename(url)

            # 获取 Content-Length
            content_length = response.headers.get('Content-Length')
            if content_length:
                content_length = int(content_length)
            else:
                # 无法获取 Content-Length,无法进行完整性验证，每次必须重新下载
                content_length = 0

            # 完整文件路径
            file_info["save_path"] = os.path.join(outdir, file_info["filename"])

            # 检查本地文件是否存在且完整
            if os.path.exists(file_info["save_path"]):
                if os.path.getsize(file_info["save_path"]) == content_length:
                    print(f"本地文件 {file_info['save_path']} 已存在且完整，无需下载。")
                    return file_info

            # 下载
            with open(file_info["save_path"], 'wb') as f:
                # 使用迭代器逐块读取数据
                for chunk in response.iter_content(chunk_size=8192): 
                    if chunk:  # 过滤掉keep-alive的新块
                        f.write(chunk)

            return file_info

        except Exception as e:
            print(f"下载程序发生错误：{e}")
            file_info["save_path"] = "error"
            file_info["filename"] = "error"
            return file_info

# 检测path参数的类型和内容，并进行处理
# 如果是url，则下载和解压，获取地址和目录名
# 如果是file，则解压，获取目录名
# 如果是dir，则直接获取目录名
def get_workpath(original_path) -> dict:
    path_dict = {
        "url": "",
        "dir_path": "",
        "type": multipath(original_path),
        "name": "",
        "version": ""
    }
    # url,先下载tarball，再解压tarball
    if path_dict["type"] == "url":
        down_path = "./"
        down_abs_path = os.path.abspath(down_path)
        tarball_path_dict = download_file(original_path, down_abs_path)
        url_filename = tarball_path_dict["filename"]
        path_dict["url"] = os.path.join(os.path.dirname(original_path), url_filename)
        tarball_path = tarball_path_dict["save_path"]
        path_dict["name"] = parse_tarball(os.path.basename(tarball_path))["name"]
        path_dict["version"] = parse_tarball(os.path.basename(tarball_path))["version"]
        if tarball_path != "error":
            with tarfile.open(tarball_path, 'r') as tar:
                # 遍历 tarball 中的所有成员
                tar_level_one_member = []
                for member in tar.getmembers():
                    if '/' not in member.name.lstrip('/'):
                        tar_level_one_member.append(member)
                if len(tar_level_one_member) == 1 and tar_level_one_member[0].isdir():
                    tar.extractall(path=down_abs_path)
                    path_dict["dir_path"] = os.path.join(down_abs_path, tar_level_one_member[0].name)
                else:
                    print("不是独立的一级目录，暂未实现")
            tar.close()
    # tarball，先检测是否存在已解压目录，再解压
    elif path_dict["type"] == "file":
        tarball_path = os.path.abspath(original_path)
        path_dict["url"] = os.path.basename(tarball_path)
        path_dict["name"] = parse_tarball(os.path.basename(tarball_path))["name"]
        path_dict["version"] = parse_tarball(os.path.basename(tarball_path))["version"]

        with tarfile.open(tarball_path, 'r') as tar:
            # 遍历 tarball 中的所有成员
            tar_level_one_member = []
            for member in tar.getmembers():
                if '/' not in member.name.lstrip('/'):
                    tar_level_one_member.append(member)
            if len(tar_level_one_member) == 1 and tar_level_one_member[0].isdir():
                tar.extractall(path=os.path.dirname(tarball_path))
                path_dict["dir_path"] = os.path.join(os.path.dirname(tarball_path), tar_level_one_member[0].name)
            else:
                print("不是独立的一级目录，暂未实现")
        tar.close()
    # 指向一个目录
    elif path_dict["type"] == "dir":
        path_dict["dir_path"] = original_path
    else:
        print(original_path + "is a error path type")
        path_dict["dir_path"] = ""

    return path_dict

def get_workpath_nonex(original_path) -> dict:
    path_dict = {
        "url": "",
        "dir_path": "",
        "type": multipath(original_path),
        "name": "",
        "version": ""
    }
    # url,先下载tarball，再解压tarball
    if path_dict["type"] == "url":
        path_dict["url"] = original_path
        down_path = "./"
        down_abs_path = os.path.abspath(down_path)
        tarball_path = download_file(original_path, down_abs_path)["save_path"]
        path_dict["name"] = parse_tarball(os.path.basename(tarball_path))["name"]
        path_dict["version"] = parse_tarball(os.path.basename(tarball_path))["version"]
        if tarball_path != "error":
            with tarfile.open(tarball_path, 'r') as tar:
                # 遍历 tarball 中的所有成员
                tar_level_one_member = []
                for member in tar.getmembers():
                    if '/' not in member.name.lstrip('/'):
                        tar_level_one_member.append(member)
                if len(tar_level_one_member) == 1 and tar_level_one_member[0].isdir():
                    #tar.extractall(path=down_abs_path)
                    path_dict["dir_path"] = os.path.join(down_abs_path, tar_level_one_member[0].name)
                else:
                    print("不是独立的一级目录，暂未实现")
            tar.close()
    # tarball，先检测是否存在已解压目录，再解压
    elif path_dict["type"] == "file":
        tarball_path = os.path.abspath(original_path)
        path_dict["name"] = parse_tarball(os.path.basename(tarball_path))["name"]
        path_dict["version"] = parse_tarball(os.path.basename(tarball_path))["version"]

        with tarfile.open(tarball_path, 'r') as tar:
            # 遍历 tarball 中的所有成员
            tar_level_one_member = []
            for member in tar.getmembers():
                if '/' not in member.name.lstrip('/'):
                    tar_level_one_member.append(member)
            if len(tar_level_one_member) == 1 and tar_level_one_member[0].isdir():
                #tar.extractall(path=os.path.dirname(tarball_path))
                path_dict["dir_path"] = os.path.join(os.path.dirname(tarball_path), tar_level_one_member[0].name)
            else:
                print("不是独立的一级目录，暂未实现")
        tar.close()
    # 指向一个目录
    elif path_dict["type"] == "dir":
        path_dict["dir_path"] = original_path
    else:
        print(original_path + "is a error path type")
        path_dict["dir_path"] = ""

    return path_dict

# 从tarball中解析包名和版本号（autoconf和make构建系统需要，cmake和meson可能不需要
def parse_tarball(path):
    # is_wrap tarball顶级目录内，是一个目录，还是所有文件
    tarball_info = {
        "name": "",
        "version": "",
    }
    filename = os.path.basename(path)
    name_pattern = re.compile(r'^([^-_]+)')  # 匹配直到第一个下划线或短横线之前的所有字符作为软件名
    name_match = name_pattern.match(filename)
    if name_match:
        software_name = name_match.group(1)
        tarball_info["name"] = software_name

    version_pattern =re.compile(r'([0-9]+(?:[._-][0-9]+)*)') # 匹配*.*这种版本号
    version_match = version_pattern.search(filename)
    if version_match:
        software_version = version_match.group(0)
        software_version = software_version.replace("-", ".").replace("_", ".")
        tarball_info["version"] = software_version

    return tarball_info

def remove_comments(statements: T.List[Statement]) -> T.List[Statement]:
    """
    删除所有注释语句。

    参数:
        statements (List[Statement]): 包含注释的语句列表。

    返回:
        List[Statement]: 删除注释后的语句列表。
    """
    return [stmt for stmt in statements if stmt.name != '_']

def replace_builtin_variables(statements: T.List[Statement], builtin_variables: dict) -> T.List[Statement]:
    """
    替换所有 CMake 内置变量。

    参数:
        statements (List[Statement]): 包含变量的语句列表。
        builtin_variables (dict): 包含 CMake 内置变量的字典。

    返回:
        List[Statement]: 替换变量后的语句列表。
    """
    for stmt in statements:
        for i, arg in enumerate(stmt.args):
            if isinstance(arg, Token) and arg.tid == 'varexp':
                if arg.value in builtin_variables:
                    stmt.args[i] = Token('string', builtin_variables[arg.value])
    return statements

def replace_custom_variables(statements: T.List[Statement]) -> T.List[Statement]:
    """
    替换所有开发者自定义的变量。

    参数:
        statements (List[Statement]): 包含变量的语句列表。

    返回:
        List[Statement]: 替换变量后的语句列表。
    """
    if ENABLE_DEBUG:
        print("execute function replace_custom_variables")
    parsed_to_dict = parser_to_dict(statements)
    if not "vars" in parsed_to_dict:
        parsed_to_dict["vars"] = {}
    custom_variables = parsed_to_dict["vars"]
    for stmt in statements:
        for i, arg in enumerate(stmt.args):
            if isinstance(arg, Token) and arg.tid == 'varexp':
                if arg.value in custom_variables:
                    stmt.args[i] = Token('string', custom_variables[arg.value])
    return statements

def expand_subdirectory_files(statements: T.List[Statement], base_dir: Path) -> T.List[Statement]:
    """
    展开所有 add_subdirectory 语句，递归处理被引入的文件。

    参数:
        statements (List[Statement]): 包含 add_subdirectory 语句的语句列表。
        base_dir (Path): 当前 CMakeLists.txt 文件的目录路径。

    返回:
        List[Statement]: 展开 add_subdirectory 语句后的语句列表。
    """
    expanded_statements = statements.copy()
    parsed_to_dict = parser_to_dict(statements)
    if "add_subdirectory" in parsed_to_dict:
        subdirs = parsed_to_dict["add_subdirectory"]
        for subdir in subdirs:
            file_path = base_dir / subdir / "CMakeLists.txt"
            if str(file_path.resolve()) not in Clists["unparsed"]:
                Clists["unparsed"].append(str(file_path.resolve()))
            if file_path.exists() and (not file_path.resolve() in Clists["parsed"]):
                if file_path.is_file():  # 检查路径是否为文件
                    dir_path = base_dir / subdir
                    dir_path = dir_path.resolve()
                    if not file_path.resolve() in Clists["parsed"]:
                        include_statements = prebuild(dir_path)
                        expanded_statements.extend(include_statements)
                        parsed_to_dict["add_subdirectory"].remove(subdir)
                else:
                    print(f"Warning: {file_path} is not a file.")  # 增加调试信息
            else:
                print(f"Warning: {file_path} does not exist.")  # 增加调试信息
        return expanded_statements
    return statements

# 查询许可证
def scan_license(dirpath):
    max_depth = 0
    license_inte_list = []
    for root, _, files in os.walk(dirpath):
        current_depth = os.path.relpath(root, dirpath).count(os.sep)
        if max_depth < current_depth:
            continue

        for file in files:
            file_path = os.path.join(root, file)
            print("parsing license: " + file_path)
            file_results = scancode_api.get_licenses(file_path)
            license_spdx = file_results["detected_license_expression_spdx"]
            if license_spdx != "null" and license_spdx != None:
                license_inte_list.append(license_spdx)

    if len(license_inte_list) == 0:
        return "Unkown"

    license_list = []
    license_inte_list = list(set(license_inte_list))
    for license_inte in license_inte_list:
        license_inte_split = license_inte.split(" AND ")
        for license in license_inte_split:
            license = license.replace("(", "")
            license = license.replace(")", "")
            if license not in license_list:
                license_list.append(license)
    
    license_string = " AND ".join(license_list)
    return license_string

# 通过pkg-conf查
# 通过cmake查
# 通过/usr/lib(64)/libxxx.so查
def find_package_by_name(name):
    # 创建一个 DNF Base 对象
    base = dnf.Base()

    # 初始化 DNF Base 对象
    base.read_all_repos()

    # 填充sack，准备查询
    base.fill_sack()

    # 使用提供的文件路径查询包
    query = base.sack.query()
    filepath = ""

    # pkg-conf查rpm包
    filepath = "/lib64/pkgconfig/" + name + ".pc" 
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "pkg-conf")
        return items[0].name

    filepath = "/usr/lib64/pkgconfig/" + name + ".pc" 
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "pkg-conf")
        return items[0].name

    filepath = "/usr/share/pkgconfig/" + name + ".pc" 
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "pkg-conf")
        return items[0].name

    # cmake查
    filepath = "/usr/share/cmake/Modules/Find" + name + ".cmake"
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "cmake")
        return items[0].name

    filepath = "/usr/share/cmake/Modules/" + name + "Config.cmake"
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "cmake")
        return items[0].name

    filepath = "/usr/share/cmake/Modules/" + name + "/" + "Find" + name + ".cmake"
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "cmake")
        return items[0].name

    filepath = "/usr/share/cmake/Modules/" + name + "/" + name + "Config.cmake"
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "cmake")
        return items[0].name

    filepath = "/usr/lib64/cmake/" + name + "/" + "Find" + name + ".cmake"
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "cmake")
        return items[0].name

    filepath = "/usr/lib64/cmake/" + name + "/" + name + "Config.cmake"
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "cmake") 
        return items[0].name

    # 通过/usr/lib(64)/libxxx.so查
    filepath = "/usr/lib64/lib" + name + ".so"
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "filepath")
        return items[0].name

    filepath = "/usr/lib/lib" + name + ".so"
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "filepath")
        return items[0].name

    filepath = "/lib/lib" + name + ".so"
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "filepath")
        return items[0].name

    filepath = "/lib64/lib" + name + ".so"
    items = query.filter(file=filepath)
    if items:
        print(items[0].name + " way: " + "filepath")
        return items[0].name
    # 没查到，原样返回
    return name

def prebuild(cmake_dir_path: str) -> T.List[Statement]:
    """
    解析 CMakeLists.txt 文件，生成包含解析结果的字典

    参数:
        cmake_source (str): CMakeLists.txt 源码

    返回:
        Dict[str, Any]: 包含解析结果的字典

    todo:
        支持module.cmake文件解析
    """
    dir_path = Path(cmake_dir_path)
    file_path = dir_path / 'CMakeLists.txt'
    cmake_source = None
    with open(file_path.resolve(), 'r') as cmakefile:
        cmake_source = cmakefile.read()

    print("\033[34mParsing file: " + str(dir_path.resolve()) + "/CMakeLists.txt\033[0m")
    parser = Parser(cmake_source)
    if not str(file_path.resolve()) in Clists["parsed"]:
        Clists["parsed"].append(str(file_path.resolve()))
    Clists["unparsed"] = [item for item in Clists["unparsed"] if item not in Clists["parsed"]]
    statements = list(parser.parse())

    # 1. 删除注释
    statements = remove_comments(statements)

    # 2. 获取 CMake 内置变量
    builtin_variables = get_cmake_builtin_variables(file_path)

    # 3. 替换 CMake 内置变量
    statements = replace_builtin_variables(statements, builtin_variables)

    # 4. 替换开发者自定义的变量
    statements = replace_custom_variables(statements)

    # 5. 展开 add_subdirectory
    if "add_subdirectory" in parser_to_dict(statements):
        statements = expand_subdirectory_files(statements, file_path.parent)
    
    cmakefile.close()

    return statements

def all_dict(cmake_dir_path: str) -> T.Dict[str, T.Any]:
    to_dict = parser_to_dict(prebuild(cmake_dir_path))
    return to_dict

def parsed_dict_vars(codes, dict):
    vars_value_list={}
    # 获取${...}变量
    pattern = r'\$\{[^}]+\}'
    if ENABLE_DEBUG:
        print(f"the regex code is {codes}")
    vars_list = re.findall(pattern, codes)
    if len(vars_list) == 0:
        return codes
    for var in vars_list:
        if var[2:-1] in dict["vars"]:
            vars_value_list[var] = dict["vars"][var[2:-1]]
        else:
            vars_value_list[var] = ""
    result = re.sub(pattern, lambda match: vars_value_list.get(match.group(0), match.group(0)), codes)
    return result


def generate_spec(dict, path):
    workpath_dict = get_workpath(path)
    workpath = os.path.basename(workpath_dict["dir_path"])
    source_url = workpath_dict["url"]
    www_url = ""
    if workpath_dict["type"] == "url":
        parsed_url = urllib.parse.urlparse(source_url)
        url_parts = parsed_url.path.split('/')
        url_base_path = '/'.join(url_parts[0:3])
        www_url = f"{parsed_url.scheme}://{parsed_url.netloc}{url_base_path}"
    builddep = []
    license = scan_license(workpath)
    project_name = parsed_dict_vars(dict["find_program"]["name"], dict)
    try:
        project_name = parsed_dict_vars(dict["find_program"]["name"], dict)            
    except KeyError:
        project_name = workpath_dict["name"]
    try:
        project_version = parsed_dict_vars(dict["version"], dict)
    except KeyError:
        project_version = workpath_dict["version"]
    try:
        builddep_dict = dict["find_package"]
    except KeyError:
        builddep_dict = []
    if len(builddep_dict) > 0:
        for dep in builddep_dict.keys():
            depname = find_package_by_name(dep)
            depversion = builddep_dict[dep]["version"]
            if depversion == "":
                builddep.append(depname)
            else:
                builddep.append(depname + ">=" + depversion)
    cmake_min_version = "cmake"
    if "cmake_minimum_required" in dict:
        if not dict["cmake_minimum_required"] == "":
            cmake_min_version = "cmake >= " + dict["cmake_minimum_required"]
            builddep.append(cmake_min_version)
    language_to_builddep = {
            'C': 'gcc',
            'CXX': 'gcc-c++'
    }
    if "languages" in dict:
        for lang in dict["languages"]:
            if not language_to_builddep[lang] in dict:
                builddep.append(language_to_builddep[lang])
    

    locale.setlocale(locale.LC_TIME, 'en_US.UTF-8')
    now = datetime.now()
    date_format = "%a %b %d %Y"
    formatted_date = now.strftime(date_format)

    script_path = os.path.realpath(__file__)
    script_directory = os.path.dirname(script_path)
    specpath = script_directory + "/template.spec"
    f = open(specpath, "r", encoding= "utf-8")
    template = Template(f.read())

    result = template.render(
        summary = "A useful software named " + project_name,
        name = project_name,
        license = license,
        version = project_version,
        url = www_url,
        downloadurl = source_url,
        cmake = cmake_min_version,
        buildrequires = ', '.join(builddep),
        requires = "glibc",
        appsourcedir = workpath,
        date = formatted_date
    )

    return result, project_name

if __name__ == '__main__':
    p = argparse.ArgumentParser(description='Parse CMake file.')
    p.add_argument('path', type=str, help='dir, tarball path, or url')
    P = p.parse_args()
    workdir = get_workpath(P.path)
    cmake_dict = all_dict(workdir["dir_path"])
    print(cmake_dict)
    spec,project_name = generate_spec(cmake_dict, P.path)
    print(f"gererating spec file {project_name}.spec ...")
    file_path = project_name + '.spec'
    with open(file_path, 'w') as file:
        # 写入内容
        file.write(spec)
    file.close()